/*
 * Lombo SoCs General-Purpose Timer handling.
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt

#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>
#include "timer-of.h"
#include <linux/delay.h>

#define TIMER_SYNC_TICKS        (0x30)

/* GTimer register offsets */
#ifndef GTIMER_OFFSET_BASE
/* use gtimer1*/
#define GTIMER_OFFSET_BASE		0x30
#endif

#define GTIMER_CLK_CTRL        (GTIMER_OFFSET_BASE + 0x00)
#define GTIMER_STORE_VAL       (GTIMER_OFFSET_BASE + 0x04)
#define GTIMER_CUR_VAL         (GTIMER_OFFSET_BASE + 0x08)
#define GTIMER_CTRL            (GTIMER_OFFSET_BASE + 0x0C)
#define GTIMER_INT_EN          (GTIMER_OFFSET_BASE + 0x10)
#define GTIMER_CLR_INT_PENDING (GTIMER_OFFSET_BASE + 0x14)
#define GTIMER_INT_PENDING     (GTIMER_OFFSET_BASE + 0x18)

/* GTimer mask bit*/
#define GTIMER_NOT_READY      (0x1 << 31)  /* counter not ready status */
#define GTIMER_DOWN_COUNTING  (0x0 << 2)   /* down counting */
#define GTIMER_UP_COUNTING    (0x1 << 2)   /* up counting */
#define GTIMER_MODE_MANU      (0x0 << 1)   /* manual mode */
#define GTIMER_MODE_AUTO      (0x1 << 1)   /* auto mode */
#define GTIMER_EN             (0x1 << 0)   /* enable timer */

#define GTIMER_ENABLE		1
#define GTIMER_DISABLE		0

static void lombo_timer_hw_init(void *base);
static int lombo_timer_clkevt_next_event(unsigned long ticks,
				      struct clock_event_device *clkevt);
static int lombo_timer_clkevt_shutdown(struct clock_event_device *clkevt);
static int lombo_timer_clkevt_oneshot(struct clock_event_device *clkevt);
static int lombo_timer_clkevt_resume(struct clock_event_device *clkevt);
static void lombo_timer_resume(struct clock_event_device *clkevt);
static irqreturn_t lombo_timer_handler(int irq, void *dev_id);

static struct timer_of to = {
	.flags = TIMER_OF_IRQ | TIMER_OF_BASE | TIMER_OF_CLOCK,

	.clkevt = {
		.name		= "lombo-clkevt",
		.features	= CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_DYNIRQ,
		.rating		= 300,
		.cpumask		= cpu_possible_mask,
		.set_next_event	= lombo_timer_clkevt_next_event,
		.set_state_shutdown	= lombo_timer_clkevt_shutdown,
		.set_state_oneshot	= lombo_timer_clkevt_oneshot,
		.tick_resume		= lombo_timer_clkevt_resume,
		.resume = lombo_timer_resume,
	},

	.of_irq = {
		.flags = IRQF_TIMER | IRQF_IRQPOLL,
		.handler = lombo_timer_handler,
	},
};

static inline u32 lombo_timer_readl(void *base, unsigned long reg_offset)
{
	return readl_relaxed(base + reg_offset);
}

static inline void lombo_timer_writel(u32 val, void *base,
						unsigned long reg_offset)
{
	writel_relaxed(val, base + reg_offset);
}

/**
 * lombo_timer_barrier: write memory barrier
 * use explicit barrier to avoid using readl/writel non relaxed function
 * variants, because in our case non relaxed variants hide the true places
 * where barrier is needed.
 */
static inline void lombo_timer_barrier(void)
{
	__iowmb();
}

static void lombo_timer_ack_irq(void *base)
{
	u32 reg_val;
	/* Clear interrupt */
	reg_val = lombo_timer_readl(base, GTIMER_CLR_INT_PENDING);
	reg_val &= ~0x01;
	reg_val |= 0x01;
	lombo_timer_writel(reg_val, base, GTIMER_CLR_INT_PENDING);
	lombo_timer_readl(base, GTIMER_CLR_INT_PENDING);
}

static irqreturn_t lombo_timer_handler(int irq, void *dev_id)
{
	struct clock_event_device *clkevt = dev_id;
	struct timer_of *to = to_timer_of(clkevt);

	lombo_timer_ack_irq(timer_of_base(to));
	clkevt->event_handler(clkevt);

	return IRQ_HANDLED;
}

static void lombo_timer_enable_set(void *base, u32 enable_flag)
{
	u32 reg_val;

	reg_val = lombo_timer_readl(base, GTIMER_CTRL);

	if (!!enable_flag) {
		do {
			reg_val = lombo_timer_readl(base, GTIMER_CTRL);
		} while (reg_val & GTIMER_NOT_READY);
		lombo_timer_barrier();
		/* enable timer */
		reg_val |= (GTIMER_EN);
		lombo_timer_writel(reg_val, base, GTIMER_CTRL);
		lombo_timer_readl(base, GTIMER_CTRL);
	} else {
		/* disable timer */
		reg_val &= ~(GTIMER_EN);
		lombo_timer_writel(reg_val, base, GTIMER_CTRL);
		lombo_timer_readl(base, GTIMER_CTRL);
	}
}

static int lombo_timer_clkevt_next_event(unsigned long ticks,
				      struct clock_event_device *clkevt)
{
	struct timer_of *to = to_timer_of(clkevt);

	/* disable timer */
	lombo_timer_enable_set(timer_of_base(to), GTIMER_DISABLE);
	/* here make sure the timer has been disabled */
	udelay(400);
	/* set tick value */
	lombo_timer_writel(ticks & 0xffffffff, timer_of_base(to),
					 GTIMER_STORE_VAL);
	/*
	 * enable timer
	 */
	lombo_timer_barrier();
	lombo_timer_enable_set(timer_of_base(to), GTIMER_ENABLE);

	return 0;
}

static int lombo_timer_clkevt_shutdown(struct clock_event_device *clkevt)
{
	struct timer_of *to = to_timer_of(clkevt);

	pr_info("%s enter\n", __func__);
	/* Disable timer */
	lombo_timer_enable_set(timer_of_base(to), GTIMER_DISABLE);

	return 0;
}

static int lombo_timer_clkevt_resume(struct clock_event_device *clkevt)
{
	pr_info("%s enter\n", __func__);
	return lombo_timer_clkevt_shutdown(clkevt);
}

static int lombo_timer_clkevt_oneshot(struct clock_event_device *clkevt)
{
	pr_info("%s enter\n", __func__);
	return lombo_timer_clkevt_shutdown(clkevt);
}

static void lombo_timer_resume(struct clock_event_device *clkevt)
{
	pr_info("%s enter\n", __func__);
	lombo_timer_hw_init(timer_of_base(&to));
}

static void lombo_timer_hw_init(void *base)
{
	u32 reg_val;

	/* disable, use manual mode, */
	reg_val = lombo_timer_readl(base, GTIMER_CTRL);
	reg_val &= ~0x7u;
	lombo_timer_writel(reg_val, base, GTIMER_CTRL);

	/* here we have to be sure the timer has been disabled */
	lombo_timer_barrier();

	/* init counter to zero */
	lombo_timer_writel(0, base, GTIMER_STORE_VAL);

	/* Clear interrupt */
	reg_val = lombo_timer_readl(base, GTIMER_CLR_INT_PENDING);
	reg_val &= ~0x01;
	reg_val |= 0x01;
	lombo_timer_writel(reg_val, base, GTIMER_CLR_INT_PENDING);

	/* enable timer interrupts */
	reg_val = lombo_timer_readl(base, GTIMER_INT_EN);
	reg_val |= 0x1u;

	lombo_timer_writel(reg_val, base, GTIMER_INT_EN);
}

static int __init lombo_timer_init(struct device_node *node)
{
	int ret;

	ret = timer_of_init(node, &to);
	if (ret)
		goto err;

	lombo_timer_hw_init(timer_of_base(&to));

	clockevents_config_and_register(&to.clkevt, timer_of_rate(&to),
					TIMER_SYNC_TICKS, 0xffffffff);

	return 0;
err:
	timer_of_cleanup(&to);
	return ret;
}

TIMER_OF_DECLARE(n7_timer, "lombo,n7-gtimer", lombo_timer_init);
